#include "sdl_engine.h"
#include "sdl_binding.h"
#include "sdl_canvas.h"
#include "sdl_graphic.h"
#include "sdl_renderer.h"
#include "sdl_world.h"
#include "sdl_debug.h"
#include "sdl_vectormap.h"
#include "sdl_gametimer.h"
#include "sdl_loader3ds.h"
#include "sdl_app.h"
#include <list>
#include <vector>
#include <ctime>
#include "liblfcell.h"

	/*
	 * UWAGA: na początek uruchom program z argumentem --help
	 */

struct mesh_data {
	sdl_ptr<mvector3fmap>	v;
	sdl_ptr<mvector3fmap>	n;
	sdl_ptr<mvector2fmap>	t;
	sdl_ptr<mvector4fmap>	c;
	mesh_data(int _n,const std::string & name):
		v(new mvector3fmap(_n,name+std::string(" mesh vertices"))),
		n(new mvector3fmap(_n,name+std::string(" mesh normals"))),
		c(new mvector4fmap(_n,name+std::string(" mesh colors"))),
		t(new mvector2fmap(_n,name+std::string(" mesh texcoords")))
	{
		
	}
	~mesh_data(){
		_msg("mesh_data",40,"umieram");
	}
};

mesh_data zrob_klocka(){
	_msg("zrob_klocka()",30,"robię klocka");
	mesh_data kostka_data(36,"kostka");

	mvector3f v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,n1,n2,n3,n4,n5,n6;
//	mvector2f t1,t2,t3,t4;
	mvector4f white(1.0,1.0,1.0,1.0);

	v1=mvector3f(-1.0,-1.0,-1.0);
	v2=mvector3f(-1.0,-1.0, 1.0);
	v3=mvector3f(-1.0, 1.0,-1.0);
	v4=mvector3f(-1.0, 1.0, 1.0);
	v5=mvector3f( 1.0,-1.0,-1.0);
	v6=mvector3f( 1.0,-1.0, 1.0);
	v7=mvector3f( 1.0, 1.0,-1.0);
	v8=mvector3f( 1.0, 1.0, 1.0);

	n1=mvector3f(-1.0, 0.0, 0.0);
	n2=mvector3f( 0.0, 0.0, 1.0);
	n3=mvector3f( 1.0, 0.0, 0.0);
	n4=mvector3f( 0.0, 0.0,-1.0);
	n5=mvector3f( 0.0, 1.0, 0.0);
	n6=mvector3f( 0.0,-1.0, 0.0);

	// uwaga: nie można tutaj wrzucić inta
	mvector2f t1=mvector2f(1.0f,0.0f);
	mvector2f t2=mvector2f(1.0f,1.0f);
	mvector2f t3=mvector2f(0.0f,1.0f);
	mvector2f t4=mvector2f(0.0f,0.0f);

	// I
	(*kostka_data.v)[0]=v4;	//v1;
	(*kostka_data.v)[1]=v1;	//v4;
	(*kostka_data.v)[2]=v2;	//v3;
	(*kostka_data.v)[3]=v4;	//v1;
	(*kostka_data.v)[4]=v3;	//v2;
	(*kostka_data.v)[5]=v1;	//v4;
	// II
	(*kostka_data.v)[6]=v8; 
	(*kostka_data.v)[7]=v2; 
	(*kostka_data.v)[8]=v6; 
	(*kostka_data.v)[9]=v8; 
	(*kostka_data.v)[10]=v4; 
	(*kostka_data.v)[11]=v2; 
	//  III
	(*kostka_data.v)[12]=v7;
	(*kostka_data.v)[13]=v6;
	(*kostka_data.v)[14]=v5;
	(*kostka_data.v)[15]=v7;
	(*kostka_data.v)[16]=v8;
	(*kostka_data.v)[17]=v6;
	// IV
	(*kostka_data.v)[18]=v3; 
	(*kostka_data.v)[19]=v5;
	(*kostka_data.v)[20]=v1;
	(*kostka_data.v)[21]=v3;
	(*kostka_data.v)[22]=v7;
	(*kostka_data.v)[23]=v5;
	// V
	(*kostka_data.v)[24]=v3;
	(*kostka_data.v)[25]=v8;
	(*kostka_data.v)[26]=v7;
	(*kostka_data.v)[27]=v3;
	(*kostka_data.v)[28]=v4;
	(*kostka_data.v)[29]=v8;
	// VI
	(*kostka_data.v)[30]=v5;
	(*kostka_data.v)[31]=v2;
	(*kostka_data.v)[32]=v1;
	(*kostka_data.v)[33]=v5;
	(*kostka_data.v)[34]=v6;
	(*kostka_data.v)[35]=v2;

	// I
	(*kostka_data.t)[0]=	t1;
	(*kostka_data.t)[1]=	t3;
	(*kostka_data.t)[2]=	t2;
	(*kostka_data.t)[3]=	t1;
	(*kostka_data.t)[4]=	t4;
	(*kostka_data.t)[5]=	t3;
	// II
	(*kostka_data.t)[6]=  t1;	//t1;
	(*kostka_data.t)[7]=  t3;	//t3;
	(*kostka_data.t)[8]=  t2;	//t2;
	(*kostka_data.t)[9]=  t1;	//t1;
	(*kostka_data.t)[10]= t4;	//t4;
	(*kostka_data.t)[11]= t3;	//t3;
	// III
	(*kostka_data.t)[12]=	t1;
	(*kostka_data.t)[13]=	t3;
	(*kostka_data.t)[14]=	t2;
	(*kostka_data.t)[15]=	t1;
	(*kostka_data.t)[16]=	t4;
	(*kostka_data.t)[17]=	t3;
	// IV
	(*kostka_data.t)[18]=	t1;
	(*kostka_data.t)[19]=	t3;
	(*kostka_data.t)[20]=	t2;
	(*kostka_data.t)[21]=	t1;
	(*kostka_data.t)[22]=	t4;
	(*kostka_data.t)[23]=	t3;
	// V
	(*kostka_data.t)[24]=	t1;
	(*kostka_data.t)[25]=	t3;
	(*kostka_data.t)[26]=	t2;
	(*kostka_data.t)[27]=	t1;
	(*kostka_data.t)[28]=	t4;
	(*kostka_data.t)[29]=	t3;
	// VI
	(*kostka_data.t)[30]=	t4;
	(*kostka_data.t)[31]=	t2;
	(*kostka_data.t)[32]=	t3;
	(*kostka_data.t)[33]=	t4;
	(*kostka_data.t)[34]=	t1;
	(*kostka_data.t)[35]=	t2;
	
	for(int i=0;i<36;i++)
		(*kostka_data.c)[i]=white;
	

	(*kostka_data.n)[0]=n1;
	(*kostka_data.n)[1]=n1;
	(*kostka_data.n)[2]=n1;
	(*kostka_data.n)[3]=n1;
	(*kostka_data.n)[4]=n1;
	(*kostka_data.n)[5]=n1;

	(*kostka_data.n)[6]=n2;
	(*kostka_data.n)[7]=n2;
	(*kostka_data.n)[8]=n2;
	(*kostka_data.n)[9]=n2;
	(*kostka_data.n)[10]=n2;
	(*kostka_data.n)[11]=n2;

	(*kostka_data.n)[12]=n3;
	(*kostka_data.n)[13]=n3;
	(*kostka_data.n)[14]=n3;
	(*kostka_data.n)[15]=n3;
	(*kostka_data.n)[16]=n3;
	(*kostka_data.n)[17]=n3;

	(*kostka_data.n)[18]=n4;
	(*kostka_data.n)[19]=n4;
	(*kostka_data.n)[20]=n4;
	(*kostka_data.n)[21]=n4;
	(*kostka_data.n)[22]=n4;
	(*kostka_data.n)[23]=n4;

	(*kostka_data.n)[24]=n5;
	(*kostka_data.n)[25]=n5;
	(*kostka_data.n)[26]=n5;
	(*kostka_data.n)[27]=n5;
	(*kostka_data.n)[28]=n5;
	(*kostka_data.n)[29]=n5;

	(*kostka_data.n)[30]=n6;
	(*kostka_data.n)[31]=n6;
	(*kostka_data.n)[32]=n6;
	(*kostka_data.n)[33]=n6;
	(*kostka_data.n)[34]=n6;
	(*kostka_data.n)[35]=n6;
	return kostka_data;
};

struct myapp:public sdl_app {
	sdl_ptr<sdl_graphic::world> 	world;
	sdl_ptr<sdl_graphic::mesh>	kostka;
//	sdl_ptr<sdl_graphic::mesh>	puszka;
	sdl_ptr<sdl_canvas::screen> 	screen;
	sdl_ptr<sdl_graphic::camera>	cam;
	sdl_ptr<sdl_graphic::material>	material;
	sdl_ptr<sdl_graphic::entity> 	ship;
	sdl_ptr<sdl_graphic::world>	imported_scene;
	sdl_ptr<sdl_timer>		timer;
	sdl_ptr<sdl_base_texdata>		texture1;
	sdl_ptr<sdl_base_texdata>		texture2;
	sdl_ptr<sdl_binding> 		binding;

	float shiprotx;
	float shiproty;

	int count;		///<liczba klatek	
	mesh_data	kostka_data;

	///współrzędne tekstur
	std::vector<sdl_ptr<mvector2fmap> >	txc;

	int **board;
	myapp(int argc, char ** argv):
		sdl_app(argc,argv),
		binding(new sdl_binding),
		kostka_data(zrob_klocka()),
		timer(give<sdl_timer>()),
		kostka(new sdl_graphic::mesh("kostka")),
		imported_scene(sdl_graphic::loader3ds("coffetable_wt_chair.3DS")),	
		material(new sdl_graphic::material("betonowa ściana")),
		world(new sdl_graphic::world("labirynt")),
		cam(new sdl_graphic::camera("oczko")),
		texture1((sdl_base_texdata*)(new sdl_custom_mipmap_texdata("wall"))),
		screen(new sdl_canvas::screen(800,600,false))
	{
		(*binding)["run"]="left shift";
		(*binding)["forward"]="w";
		(*binding)["backward"]="s";
		(*binding)["up"]="r";
		(*binding)["down"]="f";
		(*binding)["left"]="a";
		(*binding)["right"]="d";
		(*binding)["reset"]="backspace";
		(*binding)["rotx-"]="q";
		(*binding)["rotx+"]="e";
		(*binding)["roty-"]="z";
		(*binding)["roty+"]="c";

		
		sdl_ptr<indexmap>	idx(new indexmap(NULL,0,kostka_data.v->size()));
		for(int i=0;i<idx->size();i++){
			(*idx)[i]=i;
		}
		// tworzymy sobie listę map współrzędnych tekstury
		// w prosty sposób można dodać drugą teksturę
		txc.push_back(kostka_data.t);
		// txc.push_back(kostka_data.t);
		// ładujemy teksturę
		texture1->load();
		// texture2->load();
		// dodajemy teksturę do materiału
		material->textures.push_back(texture1);
		// material->textures.push_back(texture2);
		// dopiero teraz tworzymy kostkę z danych (chyba, że ustawimy materiał jeszcze raz)
		kostka->create(kostka_data.v,kostka_data.n,kostka_data.c,txc,idx);
		kostka->set_material(material);

		/*
		 * kopiujemy obiekty z zaimportowanej sceny
		 */
		for(std::list<sdl_ptr<sdl_graphic::entity> >::iterator i=imported_scene->population.begin();i!=imported_scene->population.end();i++){
			world->push_back(*i);
			(*i)->transform*=scaling(mvector3f(0.08,0.08,0.08));
		//	(*i)->transform*=scaling(mvector3f(300.0,300.0,300.0));
			(*i)->transform*=translation(mvector3f(-4.5,0.0,-1.0));
			(*i)->transform*=rotation(mvector3f(1.0,0.0,0.0),-M_PI/2);
//			(*i)->transform*=rotation(mvector3f(0.0,0.0,1.0),M_PI/2);
		}
		
		// stawiamy statka
		ship= sdl_ptr<sdl_graphic::entity>( sdl_graphic::loader3ds("spaceship.3ds").import()->population.front()),
		ship->transform*=translation(mvector3f(0.0,0.0,200.0));
		//ship->transform*=rotation(mvector3f(1.0,0.0,0.0),3.14);
		world->push_back(ship);
		shiprotx=shiproty=0.0;
		

		// ustawiamy kamerę
		world->set_observer(cam);

		
		int M_SIZE=32;
		board=new int *[M_SIZE];
		for(int i=0;i<M_SIZE;++i) board[i]=new int [M_SIZE];

		for(int i=0;i<M_SIZE;++i)for(int j=0;j<M_SIZE;++j) board[i][j]=0;
		GenerateMaze(M_SIZE,M_SIZE,board);

		for(int i=0;i<M_SIZE;i++)
		 for(int j=0;j<M_SIZE;j++)
			if(board[i][j])
			{
				std::stringstream s ;
				s << "kostka " << i << "," << j;
				sdl_ptr<sdl_graphic::entity> entity(new sdl_graphic::entity(s.str()));
				entity->body = kostka;
				//entity->body = puszka;
				entity->translate((mvector3f(((double)i-M_SIZE/2)*2.0,0.0,((double)j-M_SIZE/2)*2.0)));

				world->push_back(entity);
			}
		count=0;
		
	}
	virtual ~myapp(){
		int M_SIZE=32;
		_msg("app",30,"kończę żywot");
		for(int i=0;i<M_SIZE;i++)
			delete [] board[i];
		delete [] board;
		txc.clear();
	}
	virtual void update(){
		count++;

		ship->transform.id();
		if(count%40==0)
			//_msg("app",7,"FPS: "<< timer->a_fps());
			std::cout << "FPS: "<<timer->a_fps() << std::endl;
		// kierunek "do przodu" w płaszczyźnie planszy
		mvector3f	forward(
				cam->get_los()[0],
				0.,
				cam->get_los()[2]) ;
		// kierunek prostopadły do planszy
		mvector3f	up = mvector3f(0.,1.,0.);

		if(give<sdl_input>()->lmb){
			// tutaj nie wrzucamy timera, tutaj zależność od czasu ramki jest uwzględniona automatycznie
			// wszystkie wewnętrzne transformacje obracające kamerę obracają jej osie, a tego nie chcemy zawsze
			// cam->yaw(give<sdl_input>()->m_x);
			cam->pitch(give<sdl_input>()->m_y);
			cam->rotate(up,give<sdl_input>()->m_x);
		}

		float steep=4*timer->a_dt();

		if((*binding)["run"]) {
			steep*=2;
		}
		if((*binding)["forward"])
			cam->translate(forward*steep);
		if((*binding)["backward"])
			cam->translate(-forward*steep);
		
		if((*binding)["right"])
			cam->move_right(steep);
		if((*binding)["left"])
			cam->move_left(steep);
			
		if((*binding)["up"])
			cam->translate(-up*steep);	//tutaj niestety jest na odwrót i jeszcze nie wiem jak to poprawić
		if((*binding)["down"])
			cam->translate(up*steep);
		
		if((*binding)["reset"])
			cam->reset();

		if((*binding)["rotx+"])
			shiprotx+=steep/3;
		if((*binding)["rotx-"])
			shiprotx-=steep/3;
		if((*binding)["roty+"])
			shiproty+=steep/3;
		if((*binding)["roty-"])
			shiproty-=steep/3;
		ship->transform*=scaling(mvector3f(0.1,0.1,0.1));
		ship->transform*=rotation(mvector3f(1.0,0.0,0.0),shiprotx);
		ship->transform*=rotation(mvector3f(0.0,1.0,0.0),shiproty);
		ship->transform*=translation(mvector3f(0.0,0.0,50.0));
		
	}
};

int main(int argc, char ** argv){
	// możemy włączyć wyjście do pliku (Windows - domyślnie plik, Linux - domyślnie cout)
	// give<sdl_debug::debug>()->set_output_method(sdl_debug::ofile);
	// wyłączamy nadmiarowe wiadomości
	// teraz zdefiniowane w pliku nkfsdl.ini
	// można też podać na linii poleceń
	/*
	give<sdl_debug::debug>()->set_lowpass("mvector",10);
	give<sdl_debug::debug>()->set_lowpass("renderer",10);
	give<sdl_debug::debug>()->set_lowpass("engine",10);
	give<sdl_debug::debug>()->set_lowpass("frustum",10);
	give<sdl_debug::debug>()->set_lowpass("loader3ds",5);
	*/
	// całkowite wyłączenie podnosi wydajność
	// poza tym czasem same wiadomości mogą generować błędy, szczególnie jeżeli się sięga w nich do jakichś obiektów, które mogą być niekompletne
	// inaczej opcja -n w linii poleceń
	//give<sdl_debug::debug>()->disable_messages();
	
	_msg("app",30,"tworzę mój app");
	sdl_ptr<myapp> moj_app(new myapp(argc,argv));

	// możemy ustawić ograniczenie na framerate
	// give<sdl_timer>()->set_limit(100);
	
	give<sdl_input>()->xsen=3.15;

	_msg("app",30,"wywołuję instancję renderera");
	give<sdl_graphic::renderer>()->set_world(moj_app->world);

	// showtime
	_msg("app",30,"startuję");
	moj_app->run();
	
	_msg("app",30,"--> koniec kodu!");	
	return 0;
}
